package com.mg.plugin.agrometeorology.model;

import com.mg.model.Model;
import com.mg.model.Param;
import com.mg.model.Port;
import com.mg.plugin.agrometeorology.entity.WeaData;
import com.mg.pluginexample2.ModuleBase;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class ProductionForecastBaseHistorryModel extends ModuleBase {

    public ProductionForecastBaseHistorryModel(int nID) {
        super(nID);
        m_alInputPort.add(new Port(this, Port.Type.Unknown));
        m_alInputPort.add(new Port(this, Port.Type.Unknown));
        m_alOutputPort.add(new Port(this, Port.Type.Unknown));
    }

    public String GetGroupName() {
        return "产量预报";
    }

    public String GetName() {
        return "基于产量历史丰歉气象影子指数方法";
    }

    public String GetDescription() {
        return "基于产量历史丰歉气象影子指数方法（数据必须是同一年的）";
    }

    public boolean OnAttach(Port portFrom, Port portTo) {
        try {
            int i = FindPort(portFrom, false);
            if (i != -1) {
                return true;
            }
            i = FindPort(portTo, true);
            if (i == -1) {
                return false;
            }

            ArrayList<Param> alParam = new ArrayList<Param>();
            portFrom.GetModule().GetOutputParam(portFrom, alParam);

            if (alParam.size() != 1) {
                return false;
            }

            if (alParam.get(0).m_objValue != null && !(alParam.get(0).m_objValue instanceof ArrayList)) {
                m_model.OutputLog(Model.LogLevel.Info, "alParam.get(0).m_objValue != null && !(alParam.get(0).m_objValue instanceof ArrayList)");
                return false;
            }
            if (i == 0) {
                m_model.OutputLog(Model.LogLevel.Info, "SET m_inputStationDataList");
                m_inputStationDataList = (List<String>) alParam.get(0).m_objValue;
            }
            if (i == 1) {
                m_model.OutputLog(Model.LogLevel.Info, "m_inputRealityList" + alParam.get(0).m_objValue.getClass());
                m_inputRealityList = (List<String>) alParam.get(0).m_objValue;
            }

            portTo.SetType(portFrom.GetType());
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
        return true;
    }

    public boolean OnDetach(Port portFrom, Port portTo) {
        if (!super.OnDetach(portFrom, portTo))
            return false;
        try {
            int i = FindPort(portTo, true);
            if (i == -1)
                return true;
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    //用于 参数->XML 等
    public int GetParam(ArrayList<Param> alParam) {
        try {
            super.GetParam(alParam);
            alParam.add(new Param("Date", m_strDate, "时间对应字段", "Date", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("Precipitation", m_strRain, "降水对应字段", "Precipitation", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("Temperature", m_strTemp, "温度对应字段", "Temperature", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("SunshineHour", m_strSun, "日照时数对应字段", "SunshineHour", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strDadou", m_strDadou, "大豆单产", "m_strDadou", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strYumi", m_strYumi, "玉米单产", "m_strYumi", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strShuiDao", m_strShuiDao, "水稻单产", "m_strShuiDao", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strDadouRate", m_strDadouRate, "大豆丰歉率", "m_strDadouRate", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strYumiRate", m_strYumiRate, "玉米丰歉率", "m_strYumiRate", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strShuiDaoRate", m_strShuiDaoRate, "水稻丰歉率", "m_strShuiDaoRate", "参数匹配", Param.EditType.Default));
            alParam.add(new Param("m_strStartTime", m_strStartTime, "数据开始时间", "Time", "时间", Param.EditType.Default));
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return alParam.size();
    }

    public boolean OnParamChanged(ArrayList<Param> alParam, int nIndex, Object objValue) {
        return super.OnParamChanged(alParam, nIndex, objValue);
    }

    //用于 XML->参数 等
    public int SetParam(final ArrayList<Param> alParam) {
        int i = super.SetParam(alParam);
        try {
            m_strDate = (String) alParam.get(i++).m_objValue;
            m_strRain = (String) alParam.get(i++).m_objValue;
            m_strTemp = (String) alParam.get(i++).m_objValue;
            m_strSun = (String) alParam.get(i++).m_objValue;
            m_strDadou = (String) alParam.get(i++).m_objValue;
            m_strYumi = (String) alParam.get(i++).m_objValue;
            m_strShuiDao = (String) alParam.get(i++).m_objValue;
            m_strDadouRate = (String) alParam.get(i++).m_objValue;
            m_strYumiRate = (String) alParam.get(i++).m_objValue;
            m_strShuiDaoRate = (String) alParam.get(i++).m_objValue;
            m_strStartTime = (String) alParam.get(i++).m_objValue;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return i;
    }

    public int GetOutputParam(Port port, ArrayList<Param> alParam) {
        int i = FindPort(port, false);
        if (i == -1) {
            return 0;
        }

        alParam.add(new Param("ResultString", m_listCSV));
        return alParam.size();
    }

    @SuppressWarnings("static-access")
    public boolean Execute() {

        m_inputRealityList.remove(1);
        m_inputRealityList.remove(0);
        m_inputStationDataList.addAll(m_inputRealityList);

        m_listCSV.clear();
        String head = "id,endDate,forecastType,dadouForecastRate,yumiForecastRate,shuidaoForecastRate,dadouForecast,yumiForecast,shuidaoForecast,dadouFiveForecastRate,yumiFiveForecastRate,shuidaoFiveForecastRate";
        String type = "String,String,String,Double,Double,Double,Double,Double,Double,Double,Double,Double";
        m_listCSV.add(head);
        m_listCSV.add(type);
        List<WeaData> calcWeaData = null;
        List<WeaData> weaDataList = new ArrayList<WeaData>();
        Map<String, List<WeaData>> weaDataMap = new HashMap<String, List<WeaData>>();

        SimpleDateFormat sdfYear = new SimpleDateFormat("yyyy");
        SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
        String str = m_strStartTime.replace("$ThisYear", sdfYear.format(m_model.m_dateDataStart));
        str = str.replace("$DateStart", sdfDate.format(m_model.m_dateDataStart));
        m_model.OutputLog(Model.LogLevel.Info, "STR" + str);
        Calendar dateNow = Calendar.getInstance();
        setDate(dateNow, sdfDate, str);

        if (m_inputStationDataList != null && m_inputStationDataList.size() > 0) {
            String foreHeader = m_inputStationDataList.get(0);
            if (!verifyHeader(foreHeader)) {
                m_model.OutputLog(Model.LogLevel.Error, "Header Can Not Match!");
                return false;
            } else {
                formatStationData(m_inputStationDataList, foreHeader, weaDataMap, weaDataList, dateNow, sdfDate);
            }

        }

        int year = dateNow.get(Calendar.YEAR);
        calcWeaData = weaDataMap.get(String.valueOf(year));

        int lastYear = 1949;

        WeaData calWeaData = null;
        WeaData oldWeaData = null;
        for (WeaData weaData : weaDataList) {
            List<WeaData> data = weaDataMap.get(weaData.getYear());
            double[] result = calcDValue(calcWeaData, data);

            weaData.setdTemp(result[0]);
            weaData.setdRain(result[1]);
            weaData.setdSun(result[2]);
            weaData.setAvgTemp(result[3]);
            weaData.setAvgRain(result[4]);
            weaData.setAvgSun(result[5]);

            if (year == Integer.parseInt(weaData.getYear())) {
                calWeaData = weaData;
            } else {
                if (lastYear < Integer.parseInt(weaData.getYear())) {
                    lastYear = Integer.parseInt(weaData.getYear());
                    if (weaData.getDadou() > 0) {
                        oldWeaData = weaData;
                    }
                }
            }


        }


        for (WeaData weaData : weaDataList) {
            List<WeaData> data = weaDataMap.get(weaData.getYear());
            double[] result = calcRValue(calcWeaData, data, calWeaData, weaData);

            weaData.setrTemp(result[0]);
            weaData.setrRain(result[1]);
            weaData.setrSun(result[2]);

            weaData.setCKTemp(weaData.getrTemp() / weaData.getdTemp());
            weaData.setCKRain(weaData.getrRain() / weaData.getdRain());
            weaData.setCKTemp(weaData.getrSun() / weaData.getdSun());
        }

        WeaData[] tempWeaData = {weaDataList.get(0), weaDataList.get(1), weaDataList.get(2)};
        WeaData[] rainWeaData = {weaDataList.get(0), weaDataList.get(1), weaDataList.get(2)};
        WeaData[] sunWeaData = {weaDataList.get(0), weaDataList.get(1), weaDataList.get(2)};
        WeaData[] yearWeaData = {weaDataList.get(0), weaDataList.get(1), weaDataList.get(2), weaDataList.get(3), weaDataList.get(4)};
        for (int i = 3; i < weaDataList.size(); i++) {
            WeaData weaData = weaDataList.get(i);
            tempWeaData = addToTempWeaData(weaData, tempWeaData);
            rainWeaData = addToRainWeaData(weaData, rainWeaData);
            sunWeaData = addToSunWeaData(weaData, sunWeaData);
        }

        for (int i = 5; i < weaDataList.size(); i++) {
            WeaData weaData = weaDataList.get(i);
            yearWeaData = addToYearWeaData(weaData, yearWeaData);
        }
        double[] fiveYield = calcYield(yearWeaData);

        double[] forecastRate = calcForecastRate(tempWeaData, rainWeaData, sunWeaData);
        String id = str + "_HISTORY";
        String data = id + "," + str + "," + "HISTORY," + forecastRate[0] + "," + forecastRate[1] + "," + forecastRate[2]
                + "," + oldWeaData.getDadou() * (1 + forecastRate[0])
                + "," + oldWeaData.getYumi() * (1 + forecastRate[1])
                + "," + oldWeaData.getShuidao() * (1 + forecastRate[2])
                + "," + (oldWeaData.getDadou() * (1 + forecastRate[0]) - fiveYield[0]) / fiveYield[0]
                + "," + (oldWeaData.getYumi() * (1 + forecastRate[1]) - fiveYield[1]) / fiveYield[1]
                + "," + (oldWeaData.getShuidao() * (1 + forecastRate[2]) - fiveYield[2]) / fiveYield[2];
        m_listCSV.add(data);
        OnParamChanged();
        return true;
    }

    private double[] calcYield(WeaData[] yearWeaData) {
        double dadouRate = 0;
        double yumiRate = 0;
        double shuidaoRate = 0;
        for (WeaData weaData : yearWeaData) {
            dadouRate += weaData.getDadou();
            yumiRate += weaData.getYumi();
            shuidaoRate += weaData.getShuidao();
        }
        double[] rate = {dadouRate / 5, yumiRate / 5, shuidaoRate / 5};
        return rate;
    }

    private WeaData[] addToYearWeaData(WeaData weaData, WeaData[] yearWeaData) {
        WeaData[] temp = new WeaData[6];
        temp[0] = yearWeaData[0];
        temp[1] = yearWeaData[1];
        temp[2] = yearWeaData[2];
        temp[3] = yearWeaData[3];
        temp[4] = yearWeaData[4];
        temp[5] = weaData;
        for (int i = 1; i < temp.length; i++) {
            WeaData tmp = temp[i];
            int j = i - 1;
            while (j >= 0 && Integer.parseInt(tmp.getYear()) > Integer.parseInt(temp[j].getYear())) {
                temp[j + 1] = temp[j];
                j--;
            }
            temp[j + 1] = tmp;
        }
        yearWeaData[0] = temp[0];
        yearWeaData[1] = temp[1];
        yearWeaData[2] = temp[2];
        yearWeaData[3] = temp[3];
        yearWeaData[4] = temp[4];
        return yearWeaData;
    }

    private double[] calcForecastRate(WeaData[] tempWeaData, WeaData[] rainWeaData, WeaData[] sunWeaData) {
        double dadouRate = 0;
        double yumiRate = 0;
        double shuidaoRate = 0;
        for (WeaData weaData : tempWeaData) {
            dadouRate += weaData.getDadouRate();
            yumiRate += weaData.getYumiRate();
            shuidaoRate += weaData.getShuidaoRate();
            m_model.OutputLog(Model.LogLevel.Info, "tempWeaData:" + weaData.getDate() + "DaDouRate:" + weaData.getDadouRate() + "YuMiRate" + weaData.getYumiRate() + "ShuiDaoRate" + weaData.getShuidaoRate());
        }
        for (WeaData weaData : rainWeaData) {
            dadouRate += weaData.getDadouRate();
            yumiRate += weaData.getYumiRate();
            shuidaoRate += weaData.getShuidaoRate();
            m_model.OutputLog(Model.LogLevel.Info, "rainWeaData:" + weaData.getDate() + "DaDouRate:" + weaData.getDadouRate() + "YuMiRate" + weaData.getYumiRate() + "ShuiDaoRate" + weaData.getShuidaoRate());
        }
        for (WeaData weaData : sunWeaData) {
            dadouRate += weaData.getDadouRate();
            yumiRate += weaData.getYumiRate();
            shuidaoRate += weaData.getShuidaoRate();
            m_model.OutputLog(Model.LogLevel.Info, "sunWeaData:" + weaData.getDate() + "DaDouRate:" + weaData.getDadouRate() + "YuMiRate" + weaData.getYumiRate() + "ShuiDaoRate" + weaData.getShuidaoRate());
        }
        double[] rate = {dadouRate / 9, yumiRate / 9, shuidaoRate / 9};
        return rate;
    }

    private WeaData[] addToSunWeaData(WeaData weaData, WeaData[] sunWeaData) {
        WeaData[] temp = new WeaData[4];
        temp[0] = sunWeaData[0];
        temp[1] = sunWeaData[1];
        temp[2] = sunWeaData[2];
        temp[3] = weaData;
        for (int i = 1; i < temp.length; i++) {
            WeaData tmp = temp[i];
            int j = i - 1;
            while (j >= 0 && tmp.getCKSun() > temp[j].getCKSun()) {
                temp[j + 1] = temp[j];
                j--;
            }
            temp[j + 1] = tmp;
        }
        sunWeaData[0] = temp[0];
        sunWeaData[1] = temp[1];
        sunWeaData[2] = temp[2];
        return sunWeaData;
    }

    private WeaData[] addToRainWeaData(WeaData weaData, WeaData[] rainWeaData) {
        WeaData[] temp = new WeaData[4];
        temp[0] = rainWeaData[0];
        temp[1] = rainWeaData[1];
        temp[2] = rainWeaData[2];
        temp[3] = weaData;
        for (int i = 1; i < temp.length; i++) {
            WeaData tmp = temp[i];
            int j = i - 1;
            while (j >= 0 && tmp.getCKRain() > temp[j].getCKRain()) {
                temp[j + 1] = temp[j];
                j--;
            }
            temp[j + 1] = tmp;
        }
        rainWeaData[0] = temp[0];
        rainWeaData[1] = temp[1];
        rainWeaData[2] = temp[2];
        return rainWeaData;
    }

    private WeaData[] addToTempWeaData(WeaData weaData, WeaData[] tempWeaData) {
        WeaData[] temp = new WeaData[4];
        temp[0] = tempWeaData[0];
        temp[1] = tempWeaData[1];
        temp[2] = tempWeaData[2];
        temp[3] = weaData;
        for (int i = 1; i < temp.length; i++) {
            WeaData tmp = temp[i];
            int j = i - 1;
            while (j >= 0 && tmp.getCKTemp() > temp[j].getCKTemp()) {
                temp[j + 1] = temp[j];
                j--;
            }
            temp[j + 1] = tmp;
        }
        tempWeaData[0] = temp[0];
        tempWeaData[1] = temp[1];
        tempWeaData[2] = temp[2];
        return tempWeaData;
    }

    private double[] calcRValue(List<WeaData> calcWeaData, List<WeaData> data, WeaData calWeaData, WeaData oldWeaData) {
        WeaData tmp;
        double rain = 0;
        double temp = 0;
        double sun = 0;
        double avgRain = 0;
        double avgTemp = 0;
        double avgSun = 0;
        double avgRain1 = 0;
        double avgTemp1 = 0;
        double avgSun1 = 0;
        for (WeaData weaData : data) {
            tmp = findWeaData(calcWeaData, weaData.getMMDD());
            rain += (weaData.getDeltaRain() - oldWeaData.getAvgRain()) * (tmp.getDeltaRain() - calWeaData.getAvgRain());
            temp += (weaData.getDeltaTemp() - oldWeaData.getAvgTemp()) * (tmp.getDeltaTemp() - calWeaData.getAvgTemp());
            sun += (weaData.getDeltaSun() - oldWeaData.getAvgSun()) * (tmp.getDeltaSun() - calWeaData.getAvgSun());
            avgRain += Math.pow(weaData.getDeltaRain() - oldWeaData.getAvgRain(), 2);
            avgTemp += Math.pow(weaData.getDeltaTemp() - oldWeaData.getAvgTemp(), 2);
            avgSun += Math.pow(weaData.getDeltaSun() - oldWeaData.getAvgSun(), 2);
            avgRain1 += Math.pow(tmp.getDeltaRain() - calWeaData.getAvgRain(), 2);
            avgTemp1 += Math.pow(tmp.getDeltaTemp() - calWeaData.getAvgTemp(), 2);
            avgSun1 += Math.pow(tmp.getDeltaSun() - calWeaData.getAvgSun(), 2);
        }
        double[] result = new double[3];
        result[0] = temp / Math.sqrt(avgTemp * avgTemp1);
        result[1] = rain / Math.sqrt(avgRain * avgRain1);
        result[2] = sun / Math.sqrt(avgSun * avgSun1);
        return result;
    }

    private double[] calcDValue(List<WeaData> calcWeaData, List<WeaData> data) {
        WeaData tmp;
        double rain = 0;
        double temp = 0;
        double sun = 0;
        double avgRain = 0;
        double avgTemp = 0;
        double avgSun = 0;


        for (WeaData weaData : data) {
            tmp = findWeaData(calcWeaData, weaData.getMMDD());
            rain += Math.pow((weaData.getDeltaRain() - tmp.getDeltaRain()), 2);
            temp += Math.pow((weaData.getDeltaTemp() - tmp.getDeltaTemp()), 2);
            sun += Math.pow((weaData.getDeltaSun() - tmp.getDeltaSun()), 2);
            avgRain += weaData.getDeltaRain();
            avgTemp += weaData.getDeltaTemp();
            avgSun += weaData.getDeltaSun();
        }
        double[] result = new double[6];
        result[0] = Math.sqrt(temp);
        result[1] = Math.sqrt(rain);
        result[2] = Math.sqrt(sun);
        result[3] = avgTemp / data.size();
        result[4] = avgRain / data.size();
        result[5] = avgSun / data.size();
        return result;
    }

    private WeaData findWeaData(List<WeaData> calcWeaData, String mmdd) {
        for (WeaData weaData : calcWeaData) {
            if (weaData.getMMDD().equals(mmdd)) {
                return weaData;
            }
        }
        return null;
    }

    private void setDate(Calendar dateNow, SimpleDateFormat sdfDate, String str) {
        try {
            dateNow.setTime(sdfDate.parse(str));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    private WeaData formatStationData(List<String> inputRealityList, String realHeader, Map<String, List<WeaData>> weaDataMap, List<WeaData> weaDataList, Calendar dateNow, SimpleDateFormat sdfDate) {
        Calendar calendar = Calendar.getInstance();
        int realDateIndex = findDataIndex(realHeader, m_strDate);
        int realRainIndex = findDataIndex(realHeader, m_strRain);
        int realTempIndex = findDataIndex(realHeader, m_strTemp);
        int realSunIndex = findDataIndex(realHeader, m_strSun);

        int realDadouIndex = findDataIndex(realHeader, m_strDadou);
        int realYumiIndex = findDataIndex(realHeader, m_strYumi);
        int realShuiDaoIndex = findDataIndex(realHeader, m_strShuiDao);
        int realDadouRateIndex = findDataIndex(realHeader, m_strDadouRate);
        int realYumiRateIndex = findDataIndex(realHeader, m_strYumiRate);
        int realShuiDaoRateIndex = findDataIndex(realHeader, m_strShuiDaoRate);

        String date = "";
        int dayNow = dateNow.get(Calendar.DAY_OF_MONTH);
        int monthNow = dateNow.get(Calendar.MONTH);
        for (int i = 2; i < inputRealityList.size(); i++) {
            String[] data = inputRealityList.get(i).split(",");

            date = data[realDateIndex];
            setDate(calendar, sdfDate, date);
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            int month = calendar.get(Calendar.MONTH);
            String year = String.valueOf(calendar.get(Calendar.YEAR));

            WeaData weaData = new WeaData();
            weaData.setDate(date);
            weaData.setYear(year);
            weaData.setMMDD(month + "-" + day);
            weaData.setDeltaRain(Float.parseFloat(data[realRainIndex]));
            weaData.setDeltaTemp(Float.parseFloat(data[realTempIndex]));
            weaData.setDeltaSun(Float.parseFloat(data[realSunIndex]));

            if (!data[realDadouRateIndex].equals("null")) {
                weaData.setDadou(Float.parseFloat(data[realDadouIndex]));
                weaData.setYumi(Float.parseFloat(data[realYumiIndex]));
                weaData.setShuidao(Float.parseFloat(data[realShuiDaoIndex]));
                weaData.setDadouRate(Float.parseFloat(data[realDadouRateIndex]));
                weaData.setYumiRate(Float.parseFloat(data[realYumiRateIndex]));
                weaData.setShuidaoRate(Float.parseFloat(data[realShuiDaoRateIndex]));
            }


            if (day == dayNow && month == monthNow) {
                weaDataList.add(weaData);
            }
            if (month < monthNow) {
                addToMap(weaDataMap, String.valueOf(calendar.get(Calendar.YEAR)), weaData);
            } else if (month == monthNow) {
                if (day <= dayNow) {
                    addToMap(weaDataMap, String.valueOf(calendar.get(Calendar.YEAR)), weaData);
                } else {
                    m_model.OutputLog(Model.LogLevel.Info, "WEA_DATA DO NOT FIT:DAY=" + day + "MONTH=" + month);
                }
            } else {
                m_model.OutputLog(Model.LogLevel.Info, "WEA DATA DO NOT FIT:DAY" + day + "MONTH=" + month);
            }

        }
        return null;
    }

    private void addToMap(Map<String, List<WeaData>> weaDataMap, String date, WeaData weaData) {
        if (weaDataMap.keySet().contains(date)) {
            weaDataMap.get(date).add(weaData);
        } else {
            List<WeaData> data = new ArrayList<WeaData>();
            data.add(weaData);
            weaDataMap.put(date, data);
        }
    }

    /**
     * 表头检验
     *
     * @param realHeader 实况数据表头
     * @return
     */
    private boolean verifyHeader(String realHeader) {

        if (realHeader.contains(m_strDate) && realHeader.contains(m_strTemp)) {
            if (realHeader.contains(m_strRain) && realHeader.contains(m_strSun)) {
                return true;
            }
        }
        return false;
    }

    private int findDataIndex(String realHeader, String strRain) {

        String[] data = realHeader.split(",");
        for (int i = 0; i < data.length; i++) {
            if (data[i].equalsIgnoreCase(strRain)) {
                return i;
            }
        }
        return -1;
    }

    private List<String> m_inputStationDataList = new ArrayList<String>();
    private List<String> m_inputRealityList = new ArrayList<String>();
    private List<String> m_listCSV = new ArrayList<String>();

    private String m_strDate;
    private String m_strRain;
    private String m_strTemp;
    private String m_strSun;
    private String m_strDadou;
    private String m_strYumi;
    private String m_strShuiDao;
    private String m_strDadouRate;
    private String m_strYumiRate;
    private String m_strShuiDaoRate;
    private String m_strStartTime;
}
